//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2004-2007
// Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd. or one of its subsidiaries.
//
// Garmin MapSource GDB binary files parser.
//

# include "StdAfx.h"
# include "GdbFile.h"
# include "MapSourceHelpers.h"
# include "Map.h"
# include "Types.h"
# include "GarminTypes.h"
# include "RussaTypes.h"
# include "NaviTelTypes.h"
# include "Load.h"
# include "Log.h"
# include "Status.h"
# include "Globals.h"

struct xyz_t {
	LONG lX, lY;
	double dfAltitude;
	BYTE btHasAltitude;
};

static
void _ReadXYZ (const BYTE * & _p, xyz_t & _xyz) {
	_xyz.lY = * reinterpret_cast<const LONG *> (_p);
	_p += sizeof (_xyz.lY);
	_xyz.lX = * reinterpret_cast<const LONG *> (_p);
	_p += sizeof (_xyz.lX);

	// Max Altitude.
	_xyz.btHasAltitude = * _p;
	_p += sizeof (_xyz.btHasAltitude);
	if (_xyz.btHasAltitude) {
		// In m.
		_xyz.dfAltitude = * reinterpret_cast<const double *> (_p);
		_p += sizeof (_xyz.dfAltitude);
	}
}

/////////////////////////////////////////////////////

class CGdbLoader : public CMapLoader {
  public:
	// Override.
	virtual bool IsValid (const char * _pData, size_t _cFileSize) const;
	virtual LoadStatus_t Load (const char * _pData, size_t _cFileSize, const char * _strFileName, map_t * _pMap);

  private:
	void ParseWaypoint (const CGdbWaypointHdr * _pWP,    const void * _pEnd);
	void ParseRoute    (const CGdbRouteHdr    * _pRoute, const void * _pEnd);
	void ParseTrack    (const CGdbTrackHdr    * _pTrack, const void * _pEnd);

	void _ParseText (string_t & _str, const char * _strName, size_t _cNameLen) const;
	void _ParseLinks (string_t & _strLink, const BYTE * & _p) const;

	map_t * m_pMap;
	const char * m_pStart, * m_pEnd;

	//
	size_t m_cOrdinal;

	file_refs_t m_file_refs;

	// Name-to-waypoint.
	typedef std::map<string_t, rgn_t *> name2wpt_t;
	name2wpt_t m_name2wpt;

	bool m_b2ndVersion;
	bool m_b3rdVersion;
};

DECLARE_WPTRK_LOADER (CGdbLoader, "garmin-gdb", "gdb", "Garmin GPS database (*.gdb)", ftiWTR);

bool CGdbLoader::IsValid (const char * _pData, size_t _cFileSize) const {
	if (_cFileSize < 0x2d)
		return false;
	return ::memcmp (_pData, g_pGdbSignature, sizeof (g_pGdbSignature)) == 0;
}

void CGdbLoader::_ParseText (string_t & _str, const char * _strName, size_t _cNameLen) const {
	if (m_b3rdVersion) {
		// Convert UTF-8 to Unicode.
		const size_t cNameLen0 = _cNameLen*2;
		wchar_t * const wstrName = (wchar_t *) ::alloca (sizeof (wchar_t)*cNameLen0);
		const size_t cNameLen1 = ::MultiByteToWideChar (CP_UTF8, 0, _strName, _cNameLen, wstrName, cNameLen0);

		// Convert Unicode to ANSI.
		const size_t cNameLen2 = cNameLen1*2;
		char * const strName = (char *) ::alloca (sizeof (char)*cNameLen2);
		const size_t cNameLen3 = ::WideCharToMultiByte (CP_ACP, 0, wstrName, cNameLen1, strName, cNameLen2, NULL, NULL);

		_str.assign (strName, cNameLen3);
	} else
		_str.assign (_strName, _cNameLen);
}

void CGdbLoader::_ParseLinks (string_t & _strLink, const BYTE * & _p) const {
	const char * strLink = NULL;
	size_t cLinkLen = 0;
	if (m_b3rdVersion) {
		const DWORD dwLinks = * reinterpret_cast<const DWORD *> (_p);
		_p += sizeof (dwLinks);

		for (size_t cLink = 0; cLink < dwLinks; ++ cLink) {
			strLink = reinterpret_cast<const char *> (_p);
			cLinkLen = _p < (BYTE *) m_pEnd ? ::strlen (strLink) : 0;
			_p += cLinkLen + 1;
		}
	} else {
		strLink = reinterpret_cast<const char *> (_p);
		cLinkLen = _p < (BYTE *) m_pEnd ? ::strlen (strLink) : 0;
		_p += cLinkLen + 1;
	}

	if (cLinkLen)
		_strLink.assign (strLink, cLinkLen);
}

void CGdbLoader::ParseWaypoint (const CGdbWaypointHdr * _pWP, const void * _pEnd) {
	const size_t cNameLen = ::strlen (_pWP->strName);
	const CGdbWaypoint2 * const p2 = reinterpret_cast<const CGdbWaypoint2 *> (reinterpret_cast<const BYTE *> (_pWP->strName) + cNameLen + 1);

	point_t point;
	point.x = p2->lX*180.f/(1UL << 31);
	point.y = p2->lY*180.f/(1UL << 31);

	//
	// Parse the variable part of waypoint.
	//
	const BYTE * p = reinterpret_cast<const BYTE *> (p2) + sizeof (CGdbWaypoint2);

	// Get altitude.
	const BYTE btHasAltitude = * p;
	p += sizeof (btHasAltitude);
	double dfAltitude = 0;
	if (btHasAltitude) {
		// In m.
		dfAltitude = * reinterpret_cast<const double *> (p);
		p += sizeof (dfAltitude);
	}

	// Get comment.
	const char * const strComment = reinterpret_cast<const char *> (p);
	const size_t cCommentLen = ::strlen (strComment);
	p += cCommentLen + 1;

	// Get proximity.
	const BYTE btHasProximity = * p;
	p += sizeof (btHasProximity);
	double dfProximity = 0;
	if (btHasProximity) {
		// In m.
		dfProximity = * reinterpret_cast<const double *> (p);
		p += sizeof (dfProximity);
	}

	// How to display:
	// 0x0 - symbol, 0x1 - symbol & name, 0x2 - symbol and description.
	const DWORD dwShow = * reinterpret_cast<const DWORD *> (p);
	p += sizeof (dwShow);

	// Color?
	const DWORD dwColor = * reinterpret_cast<const DWORD *> (p);
	p += sizeof (dwColor);
	assert (dwColor == 0);

	// Get type.
	const DWORD dwType = * reinterpret_cast<const DWORD *> (p);
	p += sizeof (dwType);
	WORD wPOIType = GarminWPT2POIType (dwType);
	if (wPOIType != 0 && m_pMap->pTypeSet == & g_tsNull) {
		// TODO: better solution is required!
		if (g_map.pTypeSet == & g_tsNull)
			m_pMap->pTypeSet = & g_tsGarmin;
		else if (g_map.pTypeSet == & g_tsGarmin || g_map.pTypeSet == & g_tsRussa || g_map.pTypeSet == & g_tsNavitel)
			m_pMap->pTypeSet = g_map.pTypeSet;
		else
			wPOIType = 0;
	}

	const char * const strCity = reinterpret_cast<const char *> (p);
	const size_t cCityLen = ::strlen (strCity);
	p += cCityLen + 1;

	const char * const strState = reinterpret_cast<const char *> (p);
	const size_t cStateLen = ::strlen (strState);
	p += cStateLen + 1;

	const char * const strFacility = reinterpret_cast<const char *> (p);
	const size_t cFacilityLen = ::strlen (strFacility);
	p += cFacilityLen + 1;

	const BYTE btMapLine8 = * reinterpret_cast<const BYTE *> (p);
	p += sizeof (btMapLine8);

	// Get depth.
	const BYTE btHasDepth = * p;
	p += sizeof (btHasDepth);
	double dfDepth = 0;
	if (btHasDepth) {
		// In m.
		dfDepth = * reinterpret_cast<const double *> (p);
		p += sizeof (dfDepth);
	}

	// Unknown = 0x0000. 
	const WORD wUnknown0 = * reinterpret_cast<const WORD *> (p);
	p += sizeof (wUnknown0);
	assert (wUnknown0 == 0);

	// Predicted leg time in seconds for auto-routed routes.
	const DWORD dwLegTime = * reinterpret_cast<const DWORD *> (p);
	p += sizeof (dwLegTime);

	// Routing directions.
	const char * strRoutingDirections = reinterpret_cast<const char *> (p);
	const size_t cRoutingDirectionsLen = ::strlen (strRoutingDirections);
	p += cRoutingDirectionsLen + 1;

	// Get link.
	string_t strLink;
	_ParseLinks (strLink, p);

	// Categories.
	const WORD wCategories = * reinterpret_cast<const WORD *> (p);
	p += sizeof (wCategories);

	// Get temperature.
	const BYTE btHasTemperature = * p;
	p += sizeof (btHasTemperature);
	double dfTemperature = 0;
	if (btHasTemperature) {
		// In C degrees.
		dfTemperature = * reinterpret_cast<const double *> (p);
		p += sizeof (dfTemperature);
	}

	// Date/time in seconds since 1 Jan 1970
	const BYTE btHasTime = * p;
	p += sizeof (btHasTime);
	DWORD dwTime = 0;
	if (btHasTime) {
		dwTime = * reinterpret_cast<const DWORD *> (p);
		p += sizeof (dwTime);
	}

	if (m_b3rdVersion)
		// TODO: unknown fields, zeroes.
		p += 6;

	assert (p == _pEnd);

	//
	// Add the waypoint to the map.
	//

	rgn_t & rgn = m_pMap->CreateRgn (rkPoint);
	rgn.SetWaypointType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = reinterpret_cast<const char *> (_pWP)  - m_pStart;
	rgn.cEndPos   = reinterpret_cast<const char *> (_pEnd) - m_pStart;
	rgn.cFileLine = -1;
//	rgn.cOrdinal  = m_cOrdinal ++;

	if (cNameLen)
		_ParseText (rgn.strLabel, _pWP->strName, cNameLen);
	if (cCommentLen)
		_ParseText (rgn.strComment, strComment, cCommentLen);

	rgn.origins.push_back (rgn_origin_t ());
	rgn_origin_t & origin = rgn.origins.back ();
	origin.cLevel = 0;
	origin.point  = point;

	rgn.SetType (m_pMap->pTypeSet, wPOIType, rkPoint);

	// Extra properties.
	waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.CreateWaypoint ();

	// Link.
	pWptExtra->strURL.swap (strLink);

	// Routing directions.
	if (cRoutingDirectionsLen)
		pWptExtra->strRoutingDirections.assign (strRoutingDirections, cRoutingDirectionsLen);

	// Time.
	if (btHasTime) {
		pWptExtra->wFlags |= waypoint_info_t::defTime;
		pWptExtra->dtTime = dwTime/24./60./60. + 25569.;
	}

	// Altitude.
	if (btHasAltitude) {
		pWptExtra->wFlags |= waypoint_info_t::defAltitude;
		pWptExtra->fAltitude = dfAltitude;
	}

	// Proximity.
	if (btHasProximity) {
		pWptExtra->wFlags |= waypoint_info_t::defProximity;
		pWptExtra->fProximity = dfProximity;
	}

	// Depth.
	if (btHasDepth) {
		pWptExtra->wFlags |= waypoint_info_t::defDepth;
		pWptExtra->fDepth = dfDepth;
	}

	// Temperature.
	if (btHasTemperature) {
		pWptExtra->wFlags |= waypoint_info_t::defTemperature;
		pWptExtra->fTemperature = dfTemperature;
	}

	// Show.
	switch (dwShow) {
		case 0: pWptExtra->SetShow (waypoint_info_t::showSymbol); break;
		default:
		case 1: pWptExtra->SetShow (waypoint_info_t::showName); break;
		case 2:	pWptExtra->SetShow (waypoint_info_t::showDescription); break;
	}

	// Garmin type.
	pWptExtra->wFlags |= waypoint_info_t::defGarminType;
	pWptExtra->wGarminType = dwType;

	m_name2wpt [rgn.strLabel] = & rgn;
}

void CGdbLoader::ParseTrack (const CGdbTrackHdr * _pTrack, const void * _pEnd) {
	const size_t cNameLen = ::strlen (_pTrack->strName);

	const CGdbTrack2 * const p2 = reinterpret_cast<const CGdbTrack2 *> (reinterpret_cast<const BYTE *> (_pTrack->strName) + cNameLen + 1);
	const BYTE * p = reinterpret_cast<const BYTE *> (p2) + sizeof (CGdbTrack2);

	points_t points (p2->dwPoints);
	trackpoints_t points_extra (p2->dwPoints);

	bool bDamaged = false;

	for (size_t cPoint = 0; cPoint < p2->dwPoints || bDamaged; ++ cPoint) {
		if (reinterpret_cast<const void *> (p + 17) > m_pEnd) {
			points.resize (cPoint);
			points_extra.resize (cPoint);
			
			break;
		}

		if (p + 2*sizeof (LONG) > _pEnd) {
			if (! bDamaged) {
				if (YesNoMsgBox ("Track '%s' is probably damaged. Would you like to restore it?", _pTrack->strName)) {
					bDamaged = true;
				} else {
					points.resize (cPoint);
					points_extra.resize (cPoint);
					break;
				}
			}

			if (points.size () < cPoint + 1) {
				points.resize (cPoint*2 + 1);
				points_extra.resize (cPoint*2 + 1);
			}
		}

		xyz_t xyz;
		_ReadXYZ (p, xyz);

		// Date/time in seconds since 1 Jan 1970
		const BYTE btHasTime = * p;
		p += sizeof (btHasTime);
		DWORD dwTime = 0;
		if (btHasTime && p < (BYTE *) m_pEnd) {
			dwTime = * reinterpret_cast<const DWORD *> (p);
			p += sizeof (dwTime);
		}

		// Depth.
		const BYTE btHasDepth = * p;
		p += sizeof (btHasDepth);
		double dfDepth = 0;
		if (btHasDepth && p < (BYTE *) m_pEnd) {
			// In m.
			dfDepth = * reinterpret_cast<const double *> (p);
			p += sizeof (dfDepth);
		}

		// Temperature.
		const BYTE btHasTemperature = * p;
		p += sizeof (btHasTemperature);
		double dfTemperature = 0;
		if (btHasTemperature && p < (BYTE *) m_pEnd) {
			// In C degrees.
			dfTemperature = * reinterpret_cast<const double *> (p);
			p += sizeof (dfTemperature);
		}

		point_t & point = points [cPoint];
		point.x = xyz.lX*180.f/(1UL << 31);
		point.y = xyz.lY*180.f/(1UL << 31);

		trackpoint_info_t & point_extra = points_extra [cPoint];
		if (xyz.btHasAltitude) {
			point_extra.wFlags |= trackpoint_info_t::defAltitude;
			point_extra.fAltitude = xyz.dfAltitude;
		}
		if (btHasTime) {
			point_extra.wFlags |= trackpoint_info_t::defTime;
			point_extra.dtTime = dwTime/24./60./60. + 25569.;
		}
		if (btHasDepth) {
			point_extra.wFlags |= trackpoint_info_t::defDepth;
			point_extra.fDepth = dfDepth;
		}
		if (btHasTemperature) {
			point_extra.wFlags |= trackpoint_info_t::defTemperature;
			point_extra.fTemperature = dfTemperature;
		}
	}

	// Get links.
	string_t strLink;
	_ParseLinks (strLink, p);

	assert (p == _pEnd);

	//
	// Add the track to the map.
	//

	rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
	rgn.SetTrackType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = reinterpret_cast<const char *> (_pTrack) - m_pStart;
	rgn.cEndPos   = reinterpret_cast<const char *> (_pEnd)   - m_pStart;
	rgn.cFileLine = -1;
//	rgn.cOrdinal = m_cOrdinal ++;

	if (cNameLen)
		_ParseText (rgn.strLabel, _pTrack->strName, cNameLen);

	rgn.elements.push_back (rgn_element_t ());
	rgn_element_t & element = rgn.elements.back ();
	element.cFileOffset = (char *) _pTrack - m_pStart;

	//
	element.cLevel = 0;
	element.points.swap (points);

	// Extra properties.
	track_info_t * const pTrackExtra = rgn.pExtra->wtr.CreateTrack ();
	element.pExtra->TrackPoints.swap (points_extra);

	// Link.
	pTrackExtra->strURL.swap (strLink);

	// Show in GPS.
	if (p2->btDisplayOnGPS == 0)
		pTrackExtra->wFlags |= track_info_t::defHideInGPS;

	// Color.
	if (1 <= p2->dwColor && p2->dwColor < c_cGarminTrackColors) {
		pTrackExtra->wFlags |= track_info_t::defLineColor;
		pTrackExtra->colLine = g_GarminTrackColors [p2->dwColor];
	}
}

void CGdbLoader::ParseRoute (const CGdbRouteHdr * _pRoute, const void * _pEnd) {
	const size_t cNameLen = ::strlen (_pRoute->strName);

	const BYTE * p = reinterpret_cast<const BYTE *> (reinterpret_cast<const BYTE *> (_pRoute->strName) + cNameLen + 1);

	// Is route auto-named by MapSource?
	const BYTE btAutoNamed = * reinterpret_cast<const BYTE *> (p);
	p += sizeof (btAutoNamed);
	assert (btAutoNamed == 0 || btAutoNamed == 1);

	// ??
	const BYTE btMinMaxNotDefined = * reinterpret_cast<const BYTE *> (p);
	p += sizeof (btMinMaxNotDefined);
	assert (btMinMaxNotDefined == 0 || btMinMaxNotDefined == 1);

	if (! btMinMaxNotDefined) {
		xyz_t xyzMax;
		_ReadXYZ (p, xyzMax);

		xyz_t xyzMin;
		_ReadXYZ (p, xyzMin);
	}

	// Links.
	const DWORD dwLinks = * reinterpret_cast<const DWORD *> (p);
	p += sizeof (dwLinks);

	points_t points;
	points.reserve (dwLinks);

	routepoints_t rpoints;
	rpoints.reserve (dwLinks);

	point_t last_point;
	bool bLastPointDefined = false;

	for (size_t cLink = 0; cLink < dwLinks; ++ cLink) {
		const char * const strWaypointName = reinterpret_cast<const char *> (p);
		const size_t cWaypointNameLen = ::strlen (strWaypointName);
		p += cWaypointNameLen + 1;

		const CGdbRouteLink2 * const pLink2 = reinterpret_cast<const CGdbRouteLink2 *> (p);
		p += sizeof (CGdbRouteLink2);

		if (* p == 0xFF)
			// ?? It seems there are 8 0xFF's inserted in routes created on GPS devices.
			p += 8;

		const CGdbRouteLink3 * const pLink3 = reinterpret_cast<const CGdbRouteLink3 *> (p);
		p += sizeof (CGdbRouteLink3);

		// Link points.
		size_t cPoint;
		for (cPoint = 0; cPoint < pLink3->dwPoints; ++ cPoint) {
			xyz_t xyz;
			_ReadXYZ (p, xyz);

			point_t point;
			point.x = xyz.lX*180.f/(1UL << 31);
			point.y = xyz.lY*180.f/(1UL << 31);

			if (cPoint == 0 && cLink != 0)
				assert (point == last_point);

			// NOTE: the last point of link is equal to the first one of the next link.
			if (cPoint + 1 < pLink3->dwPoints) {
				points.push_back (point);

				routepoint_info_t rpoint;
				if (cPoint == 0) {
					string_t strWptName;
					_ParseText (strWptName, strWaypointName, cWaypointNameLen);
					const name2wpt_t::const_iterator i = m_name2wpt.find (strWptName);
					if (i != m_name2wpt.end ())
						rpoint.pWaypoint = i->second;
				}
				rpoints.push_back (rpoint);
			} else {
				last_point = point;
				bLastPointDefined = true;
			}
		}

		// Save the name and position of the last point.
		if (cLink + 1 == dwLinks) {
			assert (pLink3->dwPoints == 0);

			if (bLastPointDefined)
				points.push_back (last_point);

			routepoint_info_t rpoint;
			if (cPoint == 0) {
				const string_t strWptName (strWaypointName, cWaypointNameLen);
				const name2wpt_t::const_iterator i = m_name2wpt.find (strWptName);
				if (i != m_name2wpt.end ())
					rpoint.pWaypoint = i->second;
			}
			rpoints.push_back (rpoint);
		}

		// Separator.
		const BYTE btLastLink = * p;
		p += sizeof (btLastLink);
		assert (btLastLink == (cLink + 1 == dwLinks ? 1 : 0));

		if (cLink + 1 < dwLinks) {
			xyz_t xyzLinkMax;
			_ReadXYZ (p, xyzLinkMax);

			xyz_t xyzLinkMin;
			_ReadXYZ (p, xyzLinkMin);
		}

		if (m_b2ndVersion) {
			// ?? 0xFF
			p += 8;
		} else if (m_b3rdVersion) {
			// ?? 0xFF
			p += 8;
			// ?? 0x00
			p += 2;
		}
	}

	// Get links.
	string_t strLink;
	_ParseLinks (strLink, p);

	string_t strComment;
	if (m_b3rdVersion) {
		// TODO: 0E 00 00 00 00
		p += 5;

		_ParseText (strComment, (const char *) p, (const BYTE *) (_pEnd) - p);
		p = (const BYTE *) _pEnd;
	}

	assert (p == _pEnd);

	//
	// Add the route to the map.
	//

	rgn_t & rgn = m_pMap->CreateRgn (rkPolyline);
	rgn.SetRouteType ();

	rgn.pFileRef  = & m_file_refs.back ();
	rgn.cBeginPos = reinterpret_cast<const char *> (_pRoute) - m_pStart;
	rgn.cEndPos   = reinterpret_cast<const char *> (_pEnd)   - m_pStart;
	rgn.cFileLine = -1;
//	rgn.cOrdinal = m_cOrdinal ++;

	if (cNameLen)
		_ParseText (rgn.strLabel, _pRoute->strName, cNameLen);

	rgn.strComment.swap (strComment);

	rgn.elements.push_back (rgn_element_t ());
	rgn_element_t & element = rgn.elements.back ();
	element.cFileOffset = rgn.cBeginPos;

	//
	element.cLevel = 0;
	element.points.swap (points);

	route_info_t * const pRouteExtra = rgn.pExtra->wtr.CreateRoute ();
	pRouteExtra->points.swap (rpoints);

	pRouteExtra->strURL.swap (strLink);

	if (btAutoNamed)
		pRouteExtra->wFlags |= route_info_t::defAutoName;
}

LoadStatus_t CGdbLoader::Load (const char * _pData, size_t _cFileSize, const char * _strMapFileName, map_t * _pMap) {
	m_pMap   = _pMap;
	m_pStart = _pData;
	m_pEnd   = _pData + _cFileSize;

	m_name2wpt.clear ();

	m_cOrdinal = 0;

	m_file_refs.clear ();
	m_file_refs.push_back (file_ref_t ());
	m_file_refs.back ().strFileName = _strMapFileName;
	m_file_refs.back ().bAttachment = true;

	SetProgressStatus (0);

	m_pMap->cHeaderLen = 0;

	const char * pRecord = _pData + sizeof (g_pGdbSignature);
	do {
		if (g_bStop)
			return lsInterrupted;

		const CGdbRecordHdr * const pHdr = reinterpret_cast<const CGdbRecordHdr *> (pRecord);
		const char * pNextRecord = pRecord + sizeof (CGdbRecordHdr) + pHdr->dwLen;

		switch (pHdr->btType) {
			case 'D': { // GDB format version.
				const CGdbFormatVersion * const pVer = static_cast<const CGdbFormatVersion *> (pHdr);
				switch (pVer->wdVersion) {
					case 0x6B:
					case 0x6C:
					case 0x6D:
						ReportText ("GDB Format version %d.", pVer->wdVersion - 0x6B + 1);
						break;
					default:
						ReportText ("Format version: 0x%02X.", pVer->wdVersion);
						break;
				}
				m_b2ndVersion = pVer->wdVersion == 0x6C;
				m_b3rdVersion = pVer->wdVersion == 0x6D;
				if (pVer->wdVersion > 0x6D)
					ReportWarning ("The file is created by a version of MapSource later than 6.12. Such format was not tested, and result of file loading may be unpredictable.");
				break;
			}
			case 'A': { // MapSource version.
				const CGdbMSVersion * const pVer = static_cast<const CGdbMSVersion *> (pHdr);

				if (::_strnicmp (pNextRecord, "MapSource", 10) == 0)
					pNextRecord += 10;
				break;
			}

			case 'W': { // Waypoint.
				if (m_pMap->cHeaderLen == 0)
					m_pMap->cHeaderLen = pRecord - _pData;

				const CGdbWaypointHdr * const pWP = static_cast<const CGdbWaypointHdr *> (pHdr);
				ParseWaypoint (pWP, pNextRecord < m_pEnd ? pNextRecord : m_pEnd);
				break;
			}

			case 'T': { // Track.
				if (m_pMap->cHeaderLen == 0)
					m_pMap->cHeaderLen = pRecord - _pData;

				const CGdbTrackHdr * const pTrack = static_cast<const CGdbTrackHdr *> (pHdr);
				ParseTrack (pTrack, pNextRecord < m_pEnd ? pNextRecord : m_pEnd);
				break;
			}

			case 'R': { // Route.
				if (m_pMap->cHeaderLen == 0)
					m_pMap->cHeaderLen = pRecord - _pData;

				const CGdbRouteHdr * const pRoute = static_cast<const CGdbRouteHdr *> (pHdr);
				ParseRoute (pRoute, pNextRecord < m_pEnd ? pNextRecord : m_pEnd);
				break;
			}

			case 'L': { // Map reference.
				if (m_pMap->cHeaderLen == 0)
					m_pMap->cHeaderLen = pRecord - _pData;

				const CGdbMapHdr * const pMap = static_cast<const CGdbMapHdr *> (pHdr);
				break;
			}

			case 'V': {	// Tail mark and map set info.
				break;
			}
		}

		pRecord = pNextRecord;

		SetProgressStatus (50.*(pRecord - _pData)/_cFileSize);
	} while (pRecord < m_pEnd);

	SetProgressStatus (50);

	m_pMap->file_refs.swap (m_file_refs);

	// Suppress warning about empty Zoom etc.
	m_pMap->bNoHeader = true;

	return lsOK;
}
